package com.ruoyi.game.websocket.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.game.service.IGameWebSocketListener;
import com.ruoyi.game.websocket.event.WsIncomingEvent;
import com.ruoyi.game.websocket.message.packet.ErrorPush;
import com.ruoyi.game.websocket.message.packet.WsPacket;
import com.ruoyi.game.websocket.session.SessionInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import static com.ruoyi.game.constant.GameErrorCode.*;

@Slf4j
@Component
@RequiredArgsConstructor
public class GameWebSocketHandler extends TextWebSocketHandler {

    // 使用线程安全的 ConcurrentHashMap 来存储 WebSocket 会话和用户信息
    public static final Map<Long, Set<WebSocketSession>> ROOM_SESSIONS = new ConcurrentHashMap<>();
    public static final Map<WebSocketSession, SessionInfo> SESSION_INFO = new ConcurrentHashMap<>();
    public static final Map<Long, WebSocketSession> PLAYER_SESSION_MAP = new ConcurrentHashMap<>(); // playerId 到 session 的映射
    private final IGameWebSocketListener webSocketListener;

    private final ObjectMapper mapper = new ObjectMapper();
    private final ApplicationEventPublisher publisher;
    private final TokenService tokenService;
    public static Long getPlayerId(WebSocketSession session) {
        return SESSION_INFO.get(session).getPlayerId();
    }

    public static SessionInfo getSessionInfo(WebSocketSession session) {
        return SESSION_INFO.get(session);
    }
    public static WebSocketSession getSessionFromPlayerId(Long playerId) {
        return PLAYER_SESSION_MAP.get(playerId);
    }

    public static boolean isPlayerOnlineInRoom(Long playerId, Long roomId) {
        WebSocketSession session = getSessionFromPlayerId(playerId);
        if (session == null || !session.isOpen()) return false;
        SessionInfo info = getSessionInfo(session);
        return info != null && roomId.equals(info.getRoomId());
    }

    /* -------------- 连接建立 -------------- */
    private final ReentrantLock sessionInfoLock = new ReentrantLock();
    private final ReentrantLock roomSessionsLock = new ReentrantLock();
    private final ReentrantLock playerSessionMapLock = new ReentrantLock();
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info(">>> [WS连接建立] 尝试连接，URI={}", session.getUri());
        Map<String, String> q = parseQuery(session.getUri().getQuery());

        String roomIdStr = q.get("roomId");
        if (roomIdStr == null || roomIdStr.isBlank()) {
            log.warn("[WS连接] roomId 为空，拒绝连接。URI={}", session.getUri());
            session.close(CloseStatus.BAD_DATA);
            return;
        }

        Long roomId = Long.valueOf(roomIdStr);
        String token = q.get("token");

        if (roomId == null) {
            log.warn("[WS连接失败] 参数 roomId 缺失，query={}", session.getUri().getQuery());
            sendErrorAndClose(session, INVALID_REQUEST, "参数 roomId 缺失");
            return;
        }

        if (token == null) {
            log.warn("[WS连接失败] 参数 token 缺失，query={}", session.getUri().getQuery());
            sendErrorAndClose(session, INVALID_REQUEST, "参数 token 缺失");
            return;
        }

        Long playerId = null;
        try {
            log.info("[Token解析] 尝试解析 token：{}", token);
            LoginUser loginUser = tokenService.getLoginUserByToken(token);
            if (loginUser != null) {
                playerId = loginUser.getUser().getUserId();
                log.info("[Token解析成功] playerId={}", playerId);
            } else {
                log.warn("[Token解析失败] LoginUser为空，token={}", token);
                sendErrorAndClose(session, UNAUTHORIZED, "Token无效或过期");
                return;
            }
        } catch (Exception e) {
            log.error("[Token解析异常] token={}，异常信息：{}", token, e.getMessage(), e);
            sendErrorAndClose(session, UNAUTHORIZED, "Token解析异常");
            return;
        }

        if (playerId == null) {
            log.warn("[WS连接失败] playerId解析为空，token={}", token);
            sendErrorAndClose(session, UNAUTHORIZED, "Token无效，无法解析playerId");
            return;
        }

        Long currentRoomId = GameWebSocketHandler.getRoomIdFromPlayerId(playerId);
        webSocketListener.onPlayerBeforeReconnect(playerId, currentRoomId, roomId);

        // 清理旧的session
        WebSocketSession oldSession = PLAYER_SESSION_MAP.get(playerId);
        if (oldSession != null && !oldSession.equals(session)) {
            // 关闭旧的session
            log.info("[玩家重复连接] 关闭旧的连接，playerId={}，roomId={}", playerId, roomId);
            try {
                oldSession.close(CloseStatus.NORMAL);
            } catch (IOException e) {
                log.error("[关闭旧连接失败] playerId={}，roomId={}", playerId, roomId, e);
            }

            // 加锁并移除旧的session
            sessionInfoLock.lock();
            roomSessionsLock.lock();
            playerSessionMapLock.lock();
            try {
                SESSION_INFO.remove(oldSession);
                if (ROOM_SESSIONS.get(roomId) != null)
                    ROOM_SESSIONS.get(roomId).remove(oldSession);
                PLAYER_SESSION_MAP.remove(playerId);
            } finally {
                sessionInfoLock.unlock();
                roomSessionsLock.unlock();
                playerSessionMapLock.unlock();
            }
        }

        // 添加新的session
        sessionInfoLock.lock();
        roomSessionsLock.lock();
        playerSessionMapLock.lock();
        try {
            SESSION_INFO.put(session, new SessionInfo(playerId, roomId));
            ROOM_SESSIONS.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(session);
            PLAYER_SESSION_MAP.put(playerId, session);
        } finally {
            sessionInfoLock.unlock();
            roomSessionsLock.unlock();
            playerSessionMapLock.unlock();
        }

        List<WsPacket<?>> packets = webSocketListener.onPlayerConnectedAndBuildPackets(roomId, playerId);
        if (packets != null) {
            for (WsPacket<?> packet : packets) {
                sendPacketToSession(session, packet);
            }
        }

        log.info("[WS连接成功] 房间ID={}，玩家ID={}", roomId, playerId);
    }

    public void sendErrorAndClose(WebSocketSession session, int code, String message) {
        try {
            ErrorPush errorPush = new ErrorPush(code, message);
            WsPacket<ErrorPush> packet = new WsPacket<>("ERROR", errorPush);
            String json = mapper.writeValueAsString(packet);
            session.sendMessage(new TextMessage(json));
            log.warn("[WS] 错误发送: code={}, message={}", code, message);
        } catch (Exception e) {
            log.error("[WS] 发送错误消息失败", e);
        } finally {
            try {
                session.close(CloseStatus.BAD_DATA);
            } catch (Exception ex) {
                log.error("[WS] 关闭连接失败", ex);
            }
        }
    }

    public void sendError(WebSocketSession session, int code, String message) {
        try {
            ErrorPush errorPush = new ErrorPush(code, message);
            WsPacket<ErrorPush> packet = new WsPacket<>("ERROR", errorPush);
            String json = mapper.writeValueAsString(packet);
            session.sendMessage(new TextMessage(json));
            log.warn("[WS] 发送错误消息: code={}, message={}", code, message);
        } catch (Exception e) {
            log.error("[WS] 发送错误消息失败", e);
        }
    }

    /* -------------- 连接关闭 -------------- */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        SessionInfo info;
        synchronized (SESSION_INFO) {
            info = SESSION_INFO.remove(session);
            if (info != null) {
                Set<WebSocketSession> set = ROOM_SESSIONS.get(info.getRoomId());
                if (set != null) {
                    set.remove(session);
                    if (set.isEmpty()) {
                        ROOM_SESSIONS.remove(info.getRoomId());
                    }
                }
                PLAYER_SESSION_MAP.remove(info.getPlayerId());
            }
        }

        // ✅ 踢出房间逻辑（在 session 清理之后）
        if (info != null) {
            Long playerId = info.getPlayerId();
            Long roomId = info.getRoomId();
            webSocketListener.onPlayerBeforeDisconnect(playerId, roomId);
        }
    }

    /* -------------- 消息发送 -------------- */

    /**
     * 通过 playerId 向对应玩家发送 WebSocket 消息
     * @param playerId 玩家ID
     * @param packet 要发送的 WebSocket 消息
     */
    public void sendPacketToPlayer(Long playerId, WsPacket<?> packet) {
        WebSocketSession session = getSessionFromPlayerId(playerId);  // 获取玩家对应的 session
        if (session != null && session.isOpen()) {
            sendPacketToSession(session, packet);  // 发送消息给该玩家
            log.info("[WS] sendPacketToPlayer => playerId={} type={} data={}", playerId, packet.getType(), packet.getData());
        } else {
            log.warn("[WS] 无法找到或打开的会话，playerId={}", playerId);
        }
    }
    
    public void sendPacketToSession(WebSocketSession session, WsPacket<?> packet) {
        log.info("[SESSION] sendPacketToSession sessions={}", session);
        try {
            if (session != null && session.isOpen()) {
                SessionInfo info = SESSION_INFO.get(session);
                Long playerId = info != null ? info.getPlayerId() : null;
                Long roomId = info != null ? info.getRoomId() : null;

                String json = mapper.writeValueAsString(packet);
                session.sendMessage(new TextMessage(json));
                log.info("[WS] sendPacketToSession => roomId={} playerId={} type={} data={}",
                        roomId, playerId, packet.getType(), packet.getData());
            }
        } catch (Exception e) {
            log.error("[WS] Send packet error", e);
            sendError(session, SEND_PACKET_ERROR, "发送消息失败");
        }
    }

    public void broadcastPacketToRoom(Long roomId, WsPacket<?> packet) {
        Set<WebSocketSession> sessions = ROOM_SESSIONS.get(roomId);
        if (sessions == null) return;
        try {
            String json = mapper.writeValueAsString(packet);
            for (WebSocketSession sess : sessions) {
                if (sess != null && sess.isOpen()) {
                    SessionInfo info = SESSION_INFO.get(sess);
                    Long playerId = info != null ? info.getPlayerId() : null;
                    sess.sendMessage(new TextMessage(json));
                    log.info("[WS] broadcastPacketToRoom => roomId={} playerId={} type={} data={}",
                            roomId, playerId, packet.getType(), packet.getData());
                }
            }
        } catch (Exception e) {
            log.error("[WS] Broadcast packet error", e);
        }
    }

    public void broadcastExclude(Long roomId, WsPacket<?> packet, Long excludePlayerId) {
        Set<WebSocketSession> sessions = ROOM_SESSIONS.get(roomId);
        if (sessions == null) return;
        try {
            String json = mapper.writeValueAsString(packet);
            for (WebSocketSession sess : sessions) {
                SessionInfo info = SESSION_INFO.get(sess);
                if (info == null || info.getPlayerId().equals(excludePlayerId)) continue;
                if (sess != null && sess.isOpen()) {
                    sess.sendMessage(new TextMessage(json));
                    log.info("[WS] broadcastExclude => roomId={} to playerId={} type={} data={}",
                            roomId, info.getPlayerId(), packet.getType(), packet.getData());
                }
            }
        } catch (Exception e) {
            log.error("[WS] Broadcast exclude packet error", e);
        }
    }

    public void closeSession(WebSocketSession session, CloseStatus status) {
        try {
            if (session != null && session.isOpen()) {
                session.close(status);  // 关闭 WebSocket 会话
                log.info("[WS] WebSocket session closed with status: {}", status);

                // 获取 session 对应的玩家 ID 和房间 ID
                SessionInfo sessionInfo = SESSION_INFO.get(session);
                if (sessionInfo != null) {
                    Long playerId = sessionInfo.getPlayerId();
                    Long roomId = sessionInfo.getRoomId();

                    // 从 ROOM_SESSIONS 中移除该 session
                    Set<WebSocketSession> roomSessions = ROOM_SESSIONS.get(roomId);
                    if (roomSessions != null) {
                        roomSessions.remove(session);
                        // 如果房间内没有其他会话，移除该房间的会话集合
                        if (roomSessions.isEmpty()) {
                            ROOM_SESSIONS.remove(roomId);
                        }
                    }

                    // 从 PLAYER_SESSION_MAP 中移除该 session
                    PLAYER_SESSION_MAP.remove(playerId);

                    // 从 SESSION_INFO 中移除该 session 的信息
                    SESSION_INFO.remove(session);
                    log.info("[WS] Session info removed for playerId={} and roomId={}", playerId, roomId);
                }
            }
        } catch (Exception e) {
            log.error("[WS] Failed to close WebSocket session", e);
        }
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 直接把 JSON 字符串抛给 Dispatcher
        publisher.publishEvent(new WsIncomingEvent(session, message.getPayload()));
    }

    private Map<String, String> parseQuery(String query) {
        Map<String, String> map = new HashMap<>();
        if (query == null) return map;
        for (String kv : query.split("&")) {
            int idx = kv.indexOf('=');
            if (idx > 0) map.put(kv.substring(0, idx), kv.substring(idx + 1));
        }
        return map;
    }

    // 根据 playerId 获取绑定的房间 id
    public static Long getRoomIdFromPlayerId(Long playerId) {
        for (Map.Entry<WebSocketSession, SessionInfo> entry : SESSION_INFO.entrySet()) {
            if (entry.getValue().getPlayerId().equals(playerId)) {
                return entry.getValue().getRoomId();
            }
        }
        return null;
    }

    // 绑定 playerId 和 roomId
    public static void bindPlayerToRoom(Long playerId, Long roomId) {
        for (Map.Entry<WebSocketSession, SessionInfo> entry : SESSION_INFO.entrySet()) {
            if (entry.getValue().getPlayerId().equals(playerId)) {
                entry.getValue().setRoomId(roomId);
                break;
            }
        }
    }

    // 解绑玩家对应的房间信息
    public static void unbindPlayerFromRoom(Long playerId) {
        for (Map.Entry<WebSocketSession, SessionInfo> entry : SESSION_INFO.entrySet()) {
            SessionInfo info = entry.getValue();
            if (info.getPlayerId().equals(playerId)) {
                info.setRoomId(null); // 解绑房间ID
                break;
            }
        }
    }
}
